/*****************************************************************************
Copyright 2006 Kervin L. Pierre
    EMAIL: otlkcon@adevsoft.com

This file is part of Otlkcon.

Otlkcon is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

Otlkcon is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Lesser GNU General Public License for more details.

You should have received a copy of the Lesser GNU General Public License
along with Otlkcon; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*******************************************************************************/

#include "StdAfx.h"
#include "O_IMessage.h"
#include "otlkcon_generic_msgstore.h"
#include "otlkcon_debug.h"
#include <string>
#include <set>
//#include <rtflib.h>

O_IMessage::O_IMessage()
{
    className = _strdup("O_IMessage");

    isTransacted = true;
    hasBeenSaved = false;
    hasAttach    = false;

    m_bAttachmentsModified = FALSE;
	m_bRecipientsModified  = FALSE;

	m_lpRecipientsSnapShot  = NULL;
	m_lpAttachmentsSnapShot = NULL;

    attachmentTable = NULL;
	recipientTable  = NULL;

	m_dwRecipRowID = 0;
}

O_IMessage::~O_IMessage()
{
	if(NULL != m_lpRecipientsSnapShot)
	{
		m_lpRecipientsSnapShot->Release();
		m_lpRecipientsSnapShot = NULL;
	} // if

    if(NULL != m_lpAttachmentsSnapShot)
	{
		m_lpAttachmentsSnapShot->Release();
		m_lpAttachmentsSnapShot = NULL;
	}
}

ULONG STDMETHODCALLTYPE O_IMessage::Release(void)
{
    ULONG ret = O_IProp::Release();

    //TODO: Messages which are released before their
    //      first SaveChanges() should be deleted, I
    //      think.

    return ret;
}

void O_IMessage::Destroy()
{
    ;
}

HRESULT O_IMessage::Create(otlkcon_generic_msgstore *mstr, O_ENTRYID *par, ULONG mflags, MAPIUID ig  )
{
    HRESULT result = S_OK;
    int propCount = 0;
    SPropValue defaultProps[16] = {0};

    SizedSPropTagArray(20, recipProps) = {0};

    OTLKCON_ENTERCRITICALSECTION(&objectCS, "IMessage::Create");

    result = O_IProp::Create( mstr, ig, otlkcon_imessage_guid );
    if( result != S_OK )
    {
        OTLKCON_DEBUGBREAK
        result = E_FAIL;
        goto create_failed;
    }

    computedProperties.insert( PR_MESSAGE_SIZE );

    entryID->typeID = MAPI_MESSAGE;
    entryID->supportedInterfaces |= OTLKCON_IMESSAGE_CLASS;
    entryID->primarySupportedInterface = OTLKCON_IMESSAGE_CLASS;
    entryID->storeInstanceGuid = msgstore->entryID->storeInstanceGuid;

    if( par )
    {
        parent = (O_ENTRYID*)memObj->malloc( sizeof(O_ENTRYID) );
        memcpy(parent, par, sizeof(O_ENTRYID));
    }

    char *str1 = (char *)calloc(1, 100);
    EntryID2String(entryID, &str1);
    OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL,
        L"O_IMessage::Create()\n    ID1=%S\n", str1);
    free(str1);

    // Create the attachment table
    SizedSPropTagArray(32, attachProps);
    propCount = 0;

    //Just PR_INSTANCE_KEY for memory snapshot
	attachProps.aulPropTag[propCount++] = PR_INSTANCE_KEY;
//    attachProps.aulPropTag[propCount++] = PR_ATTACH_NUM;
//    attachProps.aulPropTag[propCount++] = PR_RECORD_KEY;
//    attachProps.aulPropTag[propCount++] = PR_RENDERING_POSITION;
//    attachProps.aulPropTag[propCount++] = PR_ATTACH_METHOD;
//    attachProps.aulPropTag[propCount++] = PR_CREATION_TIME;
//    attachProps.aulPropTag[propCount++] = PR_DISPLAY_NAME;
//    attachProps.aulPropTag[propCount++] = PR_LAST_MODIFICATION_TIME;
	attachProps.aulPropTag[propCount++] = PR_NULL;
	attachProps.aulPropTag[propCount++] = PR_NULL;
	attachProps.aulPropTag[propCount++] = PR_NULL;
	attachProps.aulPropTag[propCount++] = PR_NULL;
	attachProps.aulPropTag[propCount++] = PR_NULL;
	attachProps.aulPropTag[propCount++] = PR_NULL;
	attachProps.aulPropTag[propCount++] = PR_NULL;
    attachProps.aulPropTag[propCount++] = PR_NULL;
    attachProps.aulPropTag[propCount++] = PR_NULL;
    attachProps.aulPropTag[propCount++] = PR_NULL;
    attachProps.aulPropTag[propCount++] = PR_NULL;
    attachProps.aulPropTag[propCount++] = PR_NULL;
    attachProps.aulPropTag[propCount++] = PR_NULL;
    attachProps.aulPropTag[propCount++] = PR_NULL;
    attachProps.aulPropTag[propCount++] = PR_NULL;
	attachProps.aulPropTag[propCount++] = PR_NULL;
	attachProps.aulPropTag[propCount++] = PR_NULL;
	attachProps.aulPropTag[propCount++] = PR_NULL;
	attachProps.aulPropTag[propCount++] = PR_NULL;
	attachProps.aulPropTag[propCount++] = PR_NULL;
	attachProps.aulPropTag[propCount++] = PR_NULL;
	attachProps.aulPropTag[propCount++] = PR_NULL;
	attachProps.aulPropTag[propCount++] = PR_NULL;
	attachProps.aulPropTag[propCount++] = PR_NULL;
	attachProps.aulPropTag[propCount++] = PR_NULL;
	attachProps.aulPropTag[propCount++] = PR_NULL;
	attachProps.aulPropTag[propCount++] = PR_NULL;
	attachProps.aulPropTag[propCount++] = PR_NULL;
	attachProps.aulPropTag[propCount++] = PR_NULL;
	attachProps.aulPropTag[propCount++] = PR_NULL;
	attachProps.aulPropTag[propCount++] = PR_NULL;
    attachProps.cValues = propCount;

    // PR_MESSAGE_ATTACHMENTS
    // Attachment sub objects
    result = OpenProperty(PR_MESSAGE_ATTACHMENTS, &IID_IMAPITable, 0, 
                    MAPI_CREATE|MAPI_MODIFY, (LPUNKNOWN *)&attachmentTable);
    if( result != S_OK )
    {
        OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
            L"IMessage::SaveChanges() failed :  CreateTable() failed");
        OTLKCON_DEBUGBREAK
        result = E_FAIL;
        goto create_failed;
    }
    result = attachmentTable->Init( (LPSPropTagArray)&attachProps );
    if( result != S_OK )
    {
        OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
            L"IMessage::SaveChanges() failed :  CreateTable() failed");
        OTLKCON_DEBUGBREAK
        result = E_FAIL;
        goto create_failed;
    }
    attachmentTable->objName = memObj->_strdup("attach");

    // Create the recipient table
	
    propCount = 0;

	//Outlook first uses QueryColumns with TBL_ALL_COLUMNS
	//after that is uses Retrieved columns in SetColumns
	//because Outlook does not check for PT_ERROR
	//in queried properties subsequent ModifyRecipients call fails
	//so we set only 2 properties provided by us by default
	//It's up to the client what properties else to have
	recipProps.aulPropTag[propCount++] = PR_INSTANCE_KEY;
    //recipProps.aulPropTag[propCount++] = PR_DISPLAY_NAME;
    //recipProps.aulPropTag[propCount++] = PR_RECIPIENT_TYPE;
    recipProps.aulPropTag[propCount++] = PR_ROWID;
//    recipProps.aulPropTag[propCount++] = PR_DISPLAY_TYPE;
//    recipProps.aulPropTag[propCount++] = PR_ENTRYID;
//    recipProps.aulPropTag[propCount++] = PR_SENDER_NAME;
//    recipProps.aulPropTag[propCount++] = PR_ADDRTYPE;
//    recipProps.aulPropTag[propCount++] = PR_RESPONSIBILITY;
//    recipProps.aulPropTag[propCount++] = PR_EMAIL_ADDRESS;
//    recipProps.aulPropTag[propCount++] = PR_SPOOLER_STATUS;
//    recipProps.aulPropTag[propCount++] = PR_TRANSPORT_STATUS;
	recipProps.aulPropTag[propCount++] = PR_NULL;
	recipProps.aulPropTag[propCount++] = PR_NULL;
	recipProps.aulPropTag[propCount++] = PR_NULL;
	recipProps.aulPropTag[propCount++] = PR_NULL;
	recipProps.aulPropTag[propCount++] = PR_NULL;
	recipProps.aulPropTag[propCount++] = PR_NULL;
	recipProps.aulPropTag[propCount++] = PR_NULL;
	recipProps.aulPropTag[propCount++] = PR_NULL;
	recipProps.aulPropTag[propCount++] = PR_NULL;
	recipProps.aulPropTag[propCount++] = PR_NULL;
    recipProps.aulPropTag[propCount++] = PR_NULL;
    recipProps.aulPropTag[propCount++] = PR_NULL;
    recipProps.aulPropTag[propCount++] = PR_NULL;
    recipProps.aulPropTag[propCount++] = PR_NULL;
    recipProps.aulPropTag[propCount++] = PR_NULL;
    recipProps.aulPropTag[propCount++] = PR_NULL;
    recipProps.aulPropTag[propCount++] = PR_NULL;
    recipProps.cValues = propCount;

    // PR_MESSAGE_RECIPIENTS
    // Recipients of this message
    result = OpenProperty(PR_MESSAGE_RECIPIENTS, &IID_IMAPITable, 0, 
                    MAPI_CREATE|MAPI_MODIFY, (LPUNKNOWN *)&recipientTable);
    if( result != S_OK )
    {
        OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
            L"IMessage::Create() failed :  CreateTable() failed");
        OTLKCON_DEBUGBREAK
        result = E_FAIL;
        goto create_failed;
    }
    result = recipientTable->Init( (LPSPropTagArray)&recipProps );
    if( result != S_OK )
    {
        OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
            L"IMessage::create() failed :  CreateTable() failed");
        OTLKCON_DEBUGBREAK
        result = E_FAIL;
        goto create_failed;
    }
	
    // Get the recipient table snapshot
	result = recipientTable->GetSnapShot(&m_lpRecipientsSnapShot);
	if( result != S_OK )
	{
		OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
			L"IMessage::create() failed :  recipientTable->GetSnapShot() failed");
		OTLKCON_DEBUGBREAK;
		result = E_FAIL;
        goto create_failed;
	}

    recipientTable->objName = memObj->_strdup("recipient");
    
    // Get the attachment table snapshot
    result = attachmentTable->GetSnapShot(&m_lpAttachmentsSnapShot);
	if( result != S_OK )
	{
		OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
			L"IMessage::create() failed :  attachmentTable->GetSnapShot() failed");
		OTLKCON_DEBUGBREAK;
		result = E_FAIL;
        goto create_failed;
	}

    attachmentTable->objName = memObj->_strdup("attachment table");

    propCount = 0;
    defaultProps[propCount].ulPropTag = PR_ENTRYID;
    defaultProps[propCount].Value.bin.lpb = (LPBYTE)entryID;
    defaultProps[propCount++].Value.bin.cb  = sizeof(O_ENTRYID);

    defaultProps[propCount].ulPropTag = PR_OBJECT_TYPE;
    defaultProps[propCount++].Value.l = MAPI_MESSAGE;

    defaultProps[propCount].ulPropTag = PR_ACCESS_LEVEL;
    defaultProps[propCount++].Value.l = MAPI_MODIFY;

    if( parent )
    {
        defaultProps[propCount].ulPropTag       = PR_PARENT_ENTRYID;
        defaultProps[propCount].Value.bin.cb    = sizeof(O_ENTRYID);
        defaultProps[propCount++].Value.bin.lpb = (LPBYTE)parent;
    }

    defaultProps[propCount].ulPropTag       = PR_RECORD_KEY;
    defaultProps[propCount].Value.bin.cb    = sizeof(O_ENTRYID);
    defaultProps[propCount++].Value.bin.lpb = (LPBYTE)entryID;

    defaultProps[propCount].ulPropTag       = PR_SEARCH_KEY;
    defaultProps[propCount].Value.bin.cb    = sizeof(MAPIUID);
    defaultProps[propCount++].Value.bin.lpb = (LPBYTE)&(entryID->instanceGuid);

    defaultProps[propCount].ulPropTag       = PR_MESSAGE_CLASS;
    defaultProps[propCount++].Value.lpszA   = memObj->_strdup("IPM");

    defaultProps[propCount].ulPropTag       = PR_MESSAGE_FLAGS;
    defaultProps[propCount++].Value.l       = mflags;

    defaultProps[propCount].ulPropTag       = PR_DISPLAY_TO;
    defaultProps[propCount++].Value.lpszA   = memObj->_strdup("");

    defaultProps[propCount].ulPropTag       = PR_DISPLAY_CC;
    defaultProps[propCount++].Value.lpszA   = memObj->_strdup("");

    defaultProps[propCount].ulPropTag       = PR_DISPLAY_BCC;
    defaultProps[propCount++].Value.lpszA   = memObj->_strdup("");

    defaultProps[propCount].ulPropTag       = PR_RECIPIENT_TYPE;
    defaultProps[propCount++].Value.l       = 0;

    defaultProps[propCount].ulPropTag       = PR_HASATTACH;
    defaultProps[propCount++].Value.b       = 0;

    defaultProps[propCount].ulPropTag       = PR_OTLKCON_IMESSAGE_ATTACH_NUM;
    defaultProps[propCount++].Value.l       = 0;
    
    // FIXME: Strictly speaking, the message shouldn't export this.
    //        But this seems like a convenient place to keep it for
    //        now. 
    defaultProps[propCount].ulPropTag       = PR_MSG_STATUS;
    defaultProps[propCount++].Value.l       = 0;

    defaultProps[propCount].ulPropTag = PR_ACCESS;
    defaultProps[propCount++].Value.l = MAPI_ACCESS_DELETE 
        | MAPI_ACCESS_MODIFY | MAPI_ACCESS_READ;

    result = O_IProp::_IntSetProps(propCount, (LPSPropValue)&defaultProps, NULL );
    if( result != S_OK )
    {
        OTLKCON_DEBUGBREAK;
    }

create_end:
    OTLKCON_LEAVECRITICALSECTION(&objectCS, "IMessage::Create");
    return result;

create_failed:
    OTLKCON_LEAVECRITICALSECTION(&objectCS, "IMessage::Create");
    // FIXME: We should cleanup here
    return result;
}

HRESULT O_IMessage::GetEidFromTableName( char *tbl, O_ENTRYID **eid, const MAPIUID *sg)
{
    HRESULT result = S_OK;

    result = O_IProp::GetEidFromTableName( tbl, eid, memObj, sg);
    if( result != S_OK )
    {
        return result;
    }

    // TODO: Continue setting up the entry ID
	if(0 ==memcmp(&(*eid)->typeGuid, &otlkcon_imessage_guid, sizeof(MAPIUID))) 
	{
		(*eid)->supportedInterfaces |= OTLKCON_IMESSAGE_CLASS;
	} // if
	else
	{
		result = MAPI_E_INVALID_ENTRYID;
	} // else
    return result;
}

HRESULT O_IMessage::Load( otlkcon_generic_msgstore *mstr, char *tableID )
{
    HRESULT result = S_OK;
    O_ENTRYID *eid;

    //So far we just set reference
	memObj = mstr->memObj;
	result = GetEidFromTableName( tableID, &eid, &mstr->entryID->storeInstanceGuid);
    if( result != S_OK )
    {
		memObj = NULL;
        return result;
    }

    // TODO: Continue setting up the entry ID

    result = Load( mstr, eid );
    if( result != S_OK )
    {
        return result;
    }

    return result;
}

HRESULT O_IMessage::Load( otlkcon_generic_msgstore *mstr, O_ENTRYID *eid )
{
    HRESULT result;

    result = O_IProp::Load(mstr, eid);

	if(SUCCEEDED(result))
	{
		//Advice parent about object notification
		//result = mstr->Advise(sizeof())
		
		// PR_MESSAGE_ATTACHMENTS
		// Attachment sub objects
		result = OpenProperty(PR_MESSAGE_ATTACHMENTS, &IID_IMAPITable, 0, 
			MAPI_MODIFY, (LPUNKNOWN *)&attachmentTable);
		if( result != S_OK )
		{
			OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
				L"O_IMessage::Load failed :  ATTACHMENTS OpenTable() failed");
			OTLKCON_DEBUGBREAK
			return E_FAIL;
		}
        result = attachmentTable->GetSnapShot(&m_lpAttachmentsSnapShot);

		// PR_MESSAGE_RECIPIENTS
		// Recipients of this message
		result = OpenProperty(PR_MESSAGE_RECIPIENTS, &IID_IMAPITable, 0, 
			MAPI_MODIFY, (LPUNKNOWN *)&recipientTable);
		if( result != S_OK )
		{
			OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
				L"O_IMessage::Load failed :  RECIPIENTS OpenTable() failed");
			OTLKCON_DEBUGBREAK
			return E_FAIL;
		}
		result = recipientTable->GetSnapShot(&m_lpRecipientsSnapShot);
	} // if
    return result;
}

HRESULT O_IMessage::GetAttachmentTable(
  ULONG ulFlags,
  LPMAPITABLE *lppTable  
)
{
    HRESULT result = S_OK;
        
    if( lppTable )
    {
        result = attachmentTable->HrGetView(NULL, NULL, NULL, lppTable);
    }
    else
    {
        OTLKCON_DEBUGBREAK;
    }

    return result;
}

STDMETHODIMP O_IMessage::SaveChanges( ULONG ulFlags )
{
    HRESULT result = S_OK;
    ULONG propCount = 0;
    int sqlRes, count;
    SPropValue defaultProps[4] = {0};
    bool recalculateSubjectProperties = false;
    SYSTEMTIME currSysTime;
    FILETIME currFileTime;

	otlkcon_sqlite_helper * sqlObj = GetSqlObj();
	if(NULL == sqlObj)
	{
		return E_FAIL;	
	} // if

	OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> O_IMessage::SaveChanges");
    OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> O_IMessage::SaveChanges");

    GetSystemTime(&currSysTime);
    SystemTimeToFileTime(&currSysTime, &currFileTime);

    ULONG cProps = 0;
    SRow row = {0};
    LPSPropValue propResult = 0;
    bool setHasAttach = false;

    // Delete all attachments scheduled
    if( deletedAttachments.size() > 0 )
    {
        for( vector<O_ENTRYID *>::iterator i = deletedAttachments.begin();
                    i != deletedAttachments.end(); ++i )
        {
            result = msgstore->RemoveObject( *i, entryID );
            if( result != S_OK )
            {
                OTLKCON_DEBUGBREAK;
            }

            // FIXME: Isn't the PR_INSTANCE_KEY just the instanceGuid?
            //        If it's not then it should be, or the guid/type
            //        pair.  Using the full entryid may cause the object
            //        to disappear in the table any Outlook or anything
            //        else modifies the entry ID.
            defaultProps[0].ulPropTag     = PR_INSTANCE_KEY;
            defaultProps[0].Value.bin.cb  = sizeof(O_ENTRYID);
            defaultProps[0].Value.bin.lpb = (LPBYTE)*i;
            result = attachmentTable->HrDeleteRow((LPSPropValue)&defaultProps);
            if( result != S_OK )
            {
                OTLKCON_DEBUGBREAK;
            }
        }

        deletedAttachments.clear();
        setHasAttach = true;
		m_bAttachmentsModified = TRUE;
    }

    propCount = 0;
    memset(&defaultProps,0,sizeof(defaultProps));

    // Save any new attachments
    if( unsavedAttachments.size() > 0 )
    {
        //  TODO:  Check that these objects have not been freed
        for(vector<O_IAttach *>::iterator i = unsavedAttachments.begin(); 
            i != unsavedAttachments.end(); ++i)
        {
            result = (*i)->_IntSaveChanges( ulFlags );
        }

        unsavedAttachments.clear();
        setHasAttach = true;
		m_bAttachmentsModified = TRUE;
    }

    // Set PR_HASATTACH appropriately
    if( setHasAttach )
    {
        LPMAPITABLE lpView = NULL;
		m_lpAttachmentsSnapShot->HrGetView(NULL, NULL, 0, &lpView);
		lpView->GetRowCount( 0, &propCount );
		lpView->Release();
//		result =  GetRowCount( 0, &propCount );
        if( propCount )
        {
            hasAttach = true;
        }
        else
        {
            hasAttach = false;
        }
    }

    propCount = 0;
    memset(&defaultProps,0,sizeof(defaultProps));

    if( !hasBeenSaved )
    {
        defaultProps[propCount].ulPropTag = PR_CREATION_TIME;
        defaultProps[propCount++].Value.ft =   currFileTime;

        // Set the foreign id on this message so that
        // it can be sync'ed.
		//Henry: We do it in the In the container notification UpdateRemoteMessage
//        defaultProps[propCount].ulPropTag = PR_OTLKCON_FOREIGN_ID;
//        char *idStr = (char *)memObj->pMalloc( 50 );
//        sprintf( idStr,
//            "%0.2X%0.2X%0.2X%0.2X-%0.2X%0.2X-%0.2X%0.2X-%0.2X%0.2X-%0.2X%0.2X%0.2X%0.2X%0.2X%0.2X.ics",
//            entryID->instanceGuid.ab[0], entryID->instanceGuid.ab[1],entryID->instanceGuid.ab[2],entryID->instanceGuid.ab[3],entryID->instanceGuid.ab[4],
//            entryID->instanceGuid.ab[5],entryID->instanceGuid.ab[6],entryID->instanceGuid.ab[7],entryID->instanceGuid.ab[8],entryID->instanceGuid.ab[9],
//            entryID->instanceGuid.ab[10],entryID->instanceGuid.ab[11],entryID->instanceGuid.ab[12],entryID->instanceGuid.ab[13],entryID->instanceGuid.ab[14],
//            entryID->instanceGuid.ab[15]);
//        defaultProps[propCount++].Value.lpszA =   idStr;
    }

    defaultProps[propCount].ulPropTag = PR_LAST_MODIFICATION_TIME;
    defaultProps[propCount++].Value.ft =   currFileTime;

    if( setHasAttach )
    {
        defaultProps[propCount].ulPropTag = PR_HASATTACH;
        defaultProps[propCount++].Value.b = hasAttach?TRUE:FALSE;
		
		defaultProps[propCount].ulPropTag = PR_MESSAGE_FLAGS;
		defaultProps[propCount++].Value.l = MSGFLAG_READ|(hasAttach?MSGFLAG_HASATTACH:0);
    }

    result = O_IProp::_IntSetProps(propCount, (LPSPropValue)&defaultProps, NULL );
    if( result != S_OK )
    {
        OTLKCON_DEBUGBREAK;
    }

    // Check that certain properties have not been
    // modified or deleted.
    if( deletedProperties.find( PROP_ID(PR_SUBJECT_PREFIX) )
                    != deletedProperties.end() )
    {
        recalculateSubjectProperties = true;
    }
    else if( deletedProperties.find( PROP_ID(PR_SUBJECT) )
                    != deletedProperties.end() )
    {
        recalculateSubjectProperties = true;
    }

    // Did we change the subject properties
    sqlRes = sqlObj->sqlCmdSingleInt( &count, 0, 
        "SELECT COUNT(*) FROM %s WHERE Transact_id = %d \
                AND PropTag IN (%d, %d)",
            tableName, objNum, PR_SUBJECT, PR_SUBJECT_PREFIX );
    if( sqlRes )
    {
        OTLKCON_DEBUGBREAK;
        result = E_FAIL;
        goto savechanges_end;
    }

    if( count )
    {
        recalculateSubjectProperties = true;
    }

	//there we need to make recipients table permanent
	if(m_bRecipientsModified)
	{
		result = SaveRecipients();
	} // if
	if( FAILED(result) )
	{
		goto savechanges_end;
	}

    // make attachment table changes permanent
    if(m_bAttachmentsModified)
	{
		m_bAttachmentsModified = FALSE;
		//Henry: TODO fix that function
//		result = SaveAttachments();
	} // if
	if( FAILED(result) )
	{
		goto savechanges_end;
	}

//	SynchronizeBody();

    result = O_IProp::SaveChanges( ulFlags );
    if( FAILED(result) )
    {
        goto savechanges_end;
    }

    // Figure out PR_SUBJECT, PR_NORMALIZED_SUBJECT, and PR_SUBJECT_PREFIX
    if( recalculateSubjectProperties )
    {
        // Get the properties
        LPSPropValue propResult = 0;
        ULONG cProps;
        char *subject = NULL, *norm_subject = NULL, *subject_prefix = NULL;
        SizedSPropTagArray(3, subjectProps);
        subjectProps.cValues = 3;
        subjectProps.aulPropTag[0] = PR_SUBJECT;
        subjectProps.aulPropTag[1] = PR_SUBJECT_PREFIX;
        subjectProps.aulPropTag[2] = PR_NORMALIZED_SUBJECT;
        result = O_IProp::GetProps( (LPSPropTagArray)&subjectProps, 
            0, &cProps, &propResult );
        switch( result )
	    {
		    case MAPI_W_ERRORS_RETURNED:
		    case S_OK:			
                for( int i=0; i<2; i++ )
                {
                    if( PROP_ID(propResult[i].ulPropTag) == PROP_ID(PR_SUBJECT) )
                    {
                        if( PROP_TYPE(propResult[i].ulPropTag) == PT_STRING8 )
                        {
                            subject = memObj->_strdup(propResult[i].Value.lpszA);
                        }  
                    }
                    else if( PROP_ID(propResult[i].ulPropTag) == PROP_ID(PR_SUBJECT_PREFIX) )
                    {
                        if( PROP_TYPE(propResult[i].ulPropTag) == PT_STRING8 )
                        {
                            subject_prefix 
                                = memObj->_strdup(propResult[i].Value.lpszA);
                        }
                    }
                    else if( PROP_ID(propResult[i].ulPropTag) 
                                            == PROP_ID(PR_NORMALIZED_SUBJECT) )
                    {
                        if( PROP_TYPE(propResult[i].ulPropTag) == PT_STRING8 )
                        {
                            norm_subject 
                                = memObj->_strdup(propResult[i].Value.lpszA);
                        }
                    }
                    else
                    {
                        // Unknown property
                        OTLKCON_DEBUGBREAK;
                    }
                }

			    break;

		    default:
			    //Error condition
                OTLKCON_DEBUGBREAK;
                result = E_FAIL;
                goto savechanges_end;
        }

        // What did we get?
        if( subject == NULL )
        {
            // subject not found
            // Setting to empty string because this property should
            // always be set.
            norm_subject = memObj->_strdup("");
        }
        else
        {
            if( subject_prefix 
                    && strncmp( subject_prefix, subject, strlen(subject_prefix) ) )
            {
                // TODO: prefix does not match.  Delete it.
                ;
                memObj->free( subject_prefix );
                subject_prefix = NULL;
            }

            // FIXME: Should the prefix comparison be case sensitive or not?
            if( subject_prefix == NULL  )
            {
                // We need to calculate the prefix
                subject_prefix = strstr(subject, ": ");
                if( subject_prefix )
                {
                    // We may have a new prefix
                    ULONG prefixSize
                        = (ULONG)strlen(subject)-strlen(subject_prefix);
                    if( prefixSize < 4 )
                    {
                        subject_prefix = (char *)memObj->malloc(prefixSize+1);
                        strncpy(subject_prefix, "%s", 3);
                        //FIXME: This memory will need to be freed
                    }
                }
            }

            if( subject_prefix )
            {
                // Calculate the normalized subject 
                norm_subject = memObj->_strdup( subject+strlen(subject_prefix) );
            }
            else
            {
                norm_subject = memObj->_strdup( subject );
            } 
        }

        // Set the new properties...            
        propCount = 0;
        defaultProps[propCount].ulPropTag = PR_NORMALIZED_SUBJECT;
        defaultProps[propCount++].Value.lpszA = norm_subject;

        if( subject )
        {
            // FIXME: Debug only code.  Plus memory leak.
            objName = _strdup(subject);

            defaultProps[propCount].ulPropTag = PR_SUBJECT;
            defaultProps[propCount++].Value.lpszA = subject;
        }

        if( subject_prefix )
        {
            defaultProps[propCount].ulPropTag = PR_SUBJECT_PREFIX;
            defaultProps[propCount++].Value.lpszA = subject_prefix;
        }

        result = O_IProp::_IntSetProps(propCount, (LPSPropValue)&defaultProps, NULL );
        if( result != S_OK )
        {
            OTLKCON_DEBUGBREAK;
        }

        // Second SaveChanges() function call to register the
        // subject property modifications
        result = O_IProp::SaveChanges( ulFlags );
        if( FAILED(result) )
        {
            goto savechanges_end;
        }
    }

    // Notify listeners        
    LPNOTIFICATION notif = (LPNOTIFICATION)memObj->malloc( sizeof(NOTIFICATION) );
    notif->info.obj.cbEntryID = sizeof(O_ENTRYID);
    notif->info.obj.lpEntryID = (LPENTRYID)entryID;
    notif->info.obj.ulObjType = MAPI_MESSAGE;
    if( parent )
    {
        notif->info.obj.cbParentID = sizeof(O_ENTRYID);
        notif->info.obj.lpParentID = (LPENTRYID)(parent);
    }
	ULONG ulNotifyFlags = 0;
    
    if( parent )
    {
	    if( !hasBeenSaved )
        {
            notif->ulEventType = fnevObjectCreated;
            result = msgstore->Notify(parent, 1, notif, &ulNotifyFlags);
        }
        else
        {
            notif->ulEventType = fnevObjectModified;    
            // Notify any listener interested that this message has changed.
            result = msgstore->Notify(parent, 1, notif, &ulNotifyFlags);
        }
    }

	//there we can notify transport of new/modified message
	//So far it is just for testing export function
	{
		SPropTagArray sptMessageType = {1,{PR_MESSAGE_CLASS}};
		ULONG cValues = 1;
		LPSPropValue lpMessageType = NULL;
		HRESULT hResult = O_IProp::_IntGetProps(&sptMessageType, 0, &cValues, &lpMessageType);
		if(SUCCEEDED(hResult) && NULL != lpMessageType && 
			PT_ERROR != PROP_TYPE(lpMessageType->ulPropTag) &&
			0 == stricmp(lpMessageType->Value.lpszA, "IPM.Appointment"))
		{
			wchar_t* iCal = NULL;
            // FIXME:  Currently we export in the Container OnNotify()
            //         not here.
			//hResult = ICalExportMIMEDIR(&iCal);
			//if(NULL != iCal)
			//{
			//	if(NULL != iCal)
			//	{
			//		OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
			//			L"O_IMessage::SaveChanges:  Appointment as iCalendar:\n%s", iCal);
			//	} // if
			//} // if
			//else
			//{
			//	OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
			//		L"O_IMessage::SaveChanges:  Appointment as iCalendar FAILED");
			//} // else
		} // if
	}
	
	hasBeenSaved = true;



savechanges_end:
    OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> O_IMessage::SaveChanges");
    OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> O_IMessage::SaveChanges");
    
    return result;
}

STDMETHODIMP O_IMessage::GetProps(LPSPropTagArray lpPropTagArray,
                      ULONG        ulFlags,
                      ULONG        *lpcValues,
                      LPSPropValue *lppPropArray
)
{
    HRESULT result = S_OK;
    int size;
	
    OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> O_IMessage::GetProps");
    OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> O_IMessage::GetProps");
    
	otlkcon_sqlite_helper * sqlObj = GetSqlObj();
	if(NULL == sqlObj)
	{
		result = E_FAIL;	
        goto getprops_end;
	} // if

    // Call the superclass's GetProps()
    result = O_IProp::GetProps( lpPropTagArray, ulFlags, lpcValues,
                      lppPropArray );
    if( FAILED(result) )
    {
        OTLKCON_DEBUGBREAK;
    }

    LPSPropValue currProp;
    int sqlRes;

    for( ULONG i=0; i<*lpcValues; i++)
    {
        currProp = (*lppPropArray+i);

        switch( currProp->ulPropTag )
        {
            case PR_MESSAGE_SIZE:
                // TODO: Add the size of attachments
                //       and recipient tables.  This number
                //       is currently off because of this.
                sqlRes = sqlObj->sqlCmdSingleInt( &size, 0,
                                "SELECT SUM(size) FROM %s", tableName);
                currProp->Value.l = size;
                break;
        }
    }

getprops_end:
    OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> O_IMessage::GetProps");
    OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> O_IMessage::GetProps");
    return result;
}

HRESULT O_IMessage::OpenAttach(
  ULONG ulAttachmentNum,
  LPCIID lpInterface,
  ULONG ulFlags,
  LPATTACH *lppAttach  
)
{
    HRESULT result = S_OK;

	*lppAttach = NULL;

    O_IProp *attach = NULL;
    SPropValue prop = {0};
    ULONG propCount = 0;
    SRestriction rt = {0};
    rt.rt = RES_PROPERTY;
//    SContentRestriction cr = {0};
    ULONG objType = 0;
    LPMAPITABLE tempTable;

    prop.ulPropTag  = PR_ATTACH_NUM;
    prop.Value.l = ulAttachmentNum;
//    cr.lpProp = &prop;
//    cr.ulPropTag = PR_ATTACH_NUM;
//    rt.res.resContent = cr;
	rt.res.resProperty.relop = RELOP_EQ;
	rt.res.resProperty.ulPropTag = PR_ATTACH_NUM;
	rt.res.resProperty.lpProp = &prop;

    result = m_lpAttachmentsSnapShot->HrGetView( NULL, NULL, NULL, &tempTable );
    result = tempTable->FindRow(&rt, BOOKMARK_BEGINNING, 0);
    switch(result)
    {
        case S_OK:
        case MAPI_W_POSITION_CHANGED:
            //Row found
            ;
            break;

        case MAPI_E_NOT_FOUND:
            //Attachment was not found
            // FIXME: Correct error code?
            result = E_FAIL;
            goto openattach_end;
            break;

        default:
            OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO,  OTLKCON_LOG_TARGET_ALL,
                L"IMessage::OpenAttach() : attachmentTable->FindRow returned %X",
                result );
            OTLKCON_DEBUGBREAK;
            break;
    }

    LPSRowSet rows = NULL;
    result = tempTable->QueryRows(1, 0, &rows);
    if( result != S_OK )
    {
        OTLKCON_DEBUGBREAK;
    }

    if( rows->cRows != 1 )
    {
        // A single row was expected
        OTLKCON_DEBUGBREAK;
    }

    MAPIUID *lpAttachUID = NULL;
	O_ENTRYID *eid = NULL;
//    ULONG ulEid = 0;

    for( ULONG i = 0; i< rows->aRow[0].cValues; i++ )
    {
        if( rows->aRow[0].lpProps[i].ulPropTag
                == PR_INSTANCE_KEY )
        {
//            ulEid = rows->aRow[0].lpProps[i].Value.bin.cb;
            lpAttachUID  = (MAPIUID *)rows->aRow[0].lpProps[i].Value.bin.lpb;
			break;
        }
    }

	if(NULL != lpAttachUID)
	{
		O_ENTRYID AttachEID = {0};
		result = O_IAttach::CreateEntryID(&AttachEID,
			&msgstore->entryID->storeInstanceGuid,
			*lpAttachUID);
		if(SUCCEEDED(result))
		{
			result = msgstore->OpenEntry( 
				(ULONG)sizeof(O_ENTRYID), (LPENTRYID)&AttachEID, 
				&IID_IMAPIProp, 0, &objType,
				(LPUNKNOWN *)&attach );
			if( result != S_OK )
			{
				OTLKCON_DEBUGBREAK;
			}
			else
			{
				*lppAttach = (LPATTACH)attach;
			} // else
		} // if
	} // if

openattach_end:
	if(NULL != rows)
	{
		FreeProws(rows);
		rows = NULL;
	} // if
    return result;
}

HRESULT O_IMessage::CreateAttach( LPCIID lpInterface, ULONG ulFlags,
  ULONG *lpulAttachmentNum, LPATTACH *lppAttach         
)
{
    HRESULT result = S_OK;
    O_IAttach *attach;

    attach = new O_IAttach();
    result = attach->Create( this, otlkcon_null_guid );
    if( result != S_OK )
    {
        OTLKCON_DEBUGBREAK;
        goto createattach_end;
    }

	//No need that here
//    unsavedAttachments.push_back( attach );

    *lpulAttachmentNum = attach->attachNum;
    *lppAttach =  (LPATTACH)attach;

createattach_end:
    return result;
}

HRESULT O_IMessage::DeleteAttach(
  ULONG ulAttachmentNum,
  ULONG ulUIParam,
  LPMAPIPROGRESS lpProgress,
  ULONG ulFlags            
)
{
    HRESULT result = S_OK;
    SPropValue prop = {0};
    ULONG propCount = 0;
    SRestriction rt = {0};
    rt.rt = RES_PROPERTY;
    LPMAPITABLE tempTable;
    SContentRestriction cr = {0};

    // TODO: We should probably notify any objects in memory
    //       that we are removing the tables.

    // Get the correct table row
    prop.ulPropTag  = PR_ATTACH_NUM;
    prop.Value.l = ulAttachmentNum;
    cr.lpProp = &prop;
    cr.ulPropTag = PR_ATTACH_NUM;
    rt.res.resContent = cr;

    result = m_lpAttachmentsSnapShot->HrGetView( NULL, NULL, NULL, &tempTable );
    result = tempTable->FindRow(&rt, BOOKMARK_BEGINNING, 0);
    switch(result)
    {
        case S_OK:
        case MAPI_W_POSITION_CHANGED:
            //Row found
            ;
            break;

        case MAPI_E_NOT_FOUND:
            //Attachment was not found
            // FIXME: Correct error code?
            result = E_FAIL;
            goto deleteattach_end;
            break;

        default:
            OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO,  OTLKCON_LOG_TARGET_ALL,
                L"IMessage::DeleteAttach() : attachmentTable->FindRow returned %X",
                result );
            OTLKCON_DEBUGBREAK;
            break;
    }

    LPSRowSet rows = NULL;
    result = tempTable->QueryRows(1, 0, &rows);
    if( result != S_OK )
    {
        OTLKCON_DEBUGBREAK;
    }

    if( rows->cRows != 1 )
    {
        // A single row was expected
        OTLKCON_DEBUGBREAK;
    }

    O_ENTRYID *eid = NULL;
    ULONG ulEid;

    for( ULONG i = 0; i< rows->aRow[0].cValues; i++ )
    {
        if( PROP_ID(rows->aRow[0].lpProps[i].ulPropTag)
                == PROP_ID(PR_INSTANCE_KEY) )
        {
            ulEid = rows->aRow[0].lpProps[i].Value.bin.cb;
            eid   = (O_ENTRYID *)rows->aRow[0].lpProps[i].Value.bin.lpb;
        }
    }

    deletedAttachments.push_back( eid );

deleteattach_end:
    return result;
}

HRESULT O_IMessage::GetRecipientTable(
  ULONG ulFlags,
  LPMAPITABLE *lppTable  
)
{
    HRESULT result = S_OK;
        
    if( lppTable )
    {
        result = recipientTable->HrGetView(NULL, NULL, NULL, lppTable);
    }
    else
    {
        OTLKCON_DEBUGBREAK;
    }

    return result;
}

HRESULT O_IMessage::ModifyRecipients(
  ULONG ulFlags,
  LPADRLIST lpMods 
)
{
	Validate_IMessage_ModifyRecipients(this, ulFlags, lpMods);
	
	HRESULT hResult = E_INVALIDARG;

	TRACE_ROWSET(OTLKCON_LOG_TARGET_ALL, (LPSRowSet)lpMods, L"O_IMessage::ModifyRecipients");

	// If no flags are specified, then it means 
	// we need to replace all existing recipients with the recipient list
	// pointed to by the lpMods parameter.
	hResult = CheckRecipients(ulFlags, lpMods);
	
	if(SUCCEEDED(hResult))
	{
		if (0 == ulFlags)
		{
			ulFlags = MODRECIP_ADD;
			hResult = ClearRecipients();
		}
	} // if
	
	//Modifying recipients if checking is OK
	if(SUCCEEDED(hResult))
	{
		if(ulFlags & MODRECIP_ADD) 
		{
			//We can cast here because structs are the same
			hResult = AddRecipients((LPSRowSet)lpMods);
		} // if
		else
		{
			hResult = ModifyOrDeleteRecipients(ulFlags, (LPSRowSet)lpMods);
		} // else

		//We need to recalculate PR_DISPLAY_TO, PR_DISPLAY_CC and PR_DISPLAY_BCC
		//after successful result
		if(SUCCEEDED(hResult))
		{
			hResult = CalculateDisplayProperties();
		} // if
	} // if

    return hResult;
}

HRESULT O_IMessage::SubmitMessage(
  ULONG ulFlags  
)
{
    HRESULT result = MAPI_E_NO_SUPPORT;

    return result;
}

HRESULT O_IMessage::SetReadFlag(
  ULONG ulFlags  
)
{
    HRESULT result = MAPI_E_NO_SUPPORT;

    return result;
}

STDMETHODIMP O_IMessage::CheckRecipients(
	ULONG ulFlags,
	LPADRLIST lpRecipients)
{
	HRESULT hResult = E_INVALIDARG;

	BOOL bError = FALSE;
	for(ULONG iRecip = 0; iRecip < lpRecipients->cEntries; iRecip++)
	{
		LPADRENTRY lpEntry = &lpRecipients->aEntries[iRecip];
		
		//Empty recipient
		if(0 == lpEntry->cValues && NULL == lpEntry->rgPropVals)
		{
			continue;
		} // if

		//Wrong data
		if(NULL == lpEntry->rgPropVals || 0 == lpEntry->cValues)
		{
			bError = TRUE;
			break;
		} // if

		//improper modification
		LPSPropValue lpRowID = 
			PpropFindProp(lpEntry->rgPropVals, lpEntry->cValues, PR_ROWID);
		
		if(NULL != lpRowID && ulFlags & (MODRECIP_ADD))
		{
			bError = TRUE;
			break;
		} // if
		else if(NULL == lpRowID && ulFlags & (MODRECIP_MODIFY | MODRECIP_REMOVE))
		{
			bError = TRUE;
			break;
		} // else if

		//Missing recipient type
		LPSPropValue lpRecipientType = 
			PpropFindProp(lpEntry->rgPropVals, lpEntry->cValues, PR_RECIPIENT_TYPE);
		
		if(NULL == lpRecipientType)
		{
			bError = TRUE;
			break;
		} // if

		//Check for wrong properties
		for(ULONG iProp = 0; iProp < lpEntry->cValues; iProp++)
		{
			if(PROP_TYPE(lpEntry->rgPropVals[iProp].ulPropTag) == PT_NULL ||
				PROP_TYPE(lpEntry->rgPropVals[iProp].ulPropTag) == PT_ERROR) 
			{
				bError = TRUE;
				break;
			} // if	
		} // for

		//seems to be OK with this recipient
	} // for

	if(!bError)
	{
		hResult = S_OK;
	} // if
	return hResult;
}


STDMETHODIMP O_IMessage::ClearRecipients()
{
	HRESULT hResult = S_OK;
	
	if(NULL != m_lpRecipientsSnapShot)
	{
		ULONG cDeleted = 0;
		hResult = m_lpRecipientsSnapShot->HrDeleteRows(TAD_ALL_ROWS, NULL, &cDeleted);

		if(SUCCEEDED(hResult) && cDeleted > 0)
		{
			m_bRecipientsModified = TRUE;
		} // if
	} // if
	return hResult;
}

STDMETHODIMP O_IMessage::AddRecipients(LPSRowSet lpRecipients)
{
	HRESULT hResult = S_OK;

	SRow srRecipientRow = { 0 };
	SPropValue spvRecipientProps[64] = { 0 };
	srRecipientRow.lpProps = spvRecipientProps;

	for (ULONG iEntry=0; iEntry < lpRecipients->cRows; iEntry++)
	{
		//we just skip empty recipient
		//according to the specification that is allowed
		if(0 == lpRecipients->aRow[iEntry].cValues)
		{
			continue;
		}
		
		 // Size of spvProps - 2 for our PR_ROWID and PR_INSTANCE_KEY properties
		if(lpRecipients->aRow[iEntry].cValues > 62)
		{
			hResult = E_OUTOFMEMORY;
			OTLKCON_DEBUGBREAK;
			break;
		}
		else
		{
			srRecipientRow.cValues = 0;
			ULONG cValues = lpRecipients->aRow[iEntry].cValues;
			std::set<ULONG> recipProps;
			LPSPropValue lpRecipProps = lpRecipients->aRow[iEntry].lpProps;
			LPSPropValue pKeyProp = NULL;
			LPSPropValue pInstanceProp = NULL;

			//That just to have every property only once
			for (ULONG iProp=0; iProp<cValues; iProp++)
			{
				if(recipProps.find(lpRecipProps[iProp].ulPropTag) == recipProps.end())
				{
					spvRecipientProps[srRecipientRow.cValues] = lpRecipProps[iProp];
					recipProps.insert(lpRecipProps[iProp].ulPropTag);
					
					if(PR_ROWID == lpRecipProps[iProp].ulPropTag)
					{
						pKeyProp = &spvRecipientProps[srRecipientRow.cValues];
					} // if
					if(PR_INSTANCE_KEY == lpRecipProps[iProp].ulPropTag)
					{
						pInstanceProp = &spvRecipientProps[srRecipientRow.cValues];
					} // if
					srRecipientRow.cValues++;
				} // if
			}
			m_dwRecipRowID++;
			
			//we need to create one
			if(NULL == pKeyProp)
			{
				spvRecipientProps[srRecipientRow.cValues].ulPropTag = PR_ROWID;
				spvRecipientProps[srRecipientRow.cValues].Value.l = m_dwRecipRowID;
				pKeyProp = &spvRecipientProps[srRecipientRow.cValues];
				srRecipientRow.cValues++;
			}	
			else
			{
				//we should not do that
				//that is only possible if Outlook replaces all recipients
				//pKeyProp->Value.l = m_dwRecipRowID;
				if(m_dwRecipRowID < pKeyProp->Value.ul)
				{
					m_dwRecipRowID = pKeyProp->Value.l + 1;
				} // if
			}

			// In case the PR_INSTANCE_KEY property is not found for this recipient, add it.
			if(NULL == pInstanceProp) 
			{
				// In the table of recipients, PR_INSTANCE_KEY and PR_ROWID have the same value
				spvRecipientProps[srRecipientRow.cValues].ulPropTag = PR_INSTANCE_KEY;
				spvRecipientProps[srRecipientRow.cValues].Value.bin.cb = sizeof(DWORD);
				spvRecipientProps[srRecipientRow.cValues].Value.bin.lpb = (LPBYTE)&pKeyProp->Value.l;
				srRecipientRow.cValues++;
			} // if
			else
			{
				pInstanceProp->Value.bin.cb = sizeof(DWORD);
				pInstanceProp->Value.bin.lpb = (LPBYTE)&m_dwRecipRowID;
			}
						
			TRACE_ROW(OTLKCON_LOG_TARGET_ALL, &srRecipientRow, L"O_IMessage::AddRecipients");
			hResult = m_lpRecipientsSnapShot->HrModifyRow(&srRecipientRow);
			if(FAILED(hResult))
			{
				break;
			} // if
			else
			{
				m_bRecipientsModified = TRUE;
			} // else
		}
	}
	
	return hResult;
}

STDMETHODIMP O_IMessage::ModifyOrDeleteRecipients(ULONG ulFlags, LPSRowSet lpRecipients)
{
	//we need to check if PR_INSTANCE_KEY is present and
	//add one if needed
	HRESULT hResult = S_OK;
	SRow srRecipientRow = { 0 };
	SPropValue spvRecipientProps[64] = { 0 };
	srRecipientRow.lpProps = spvRecipientProps;

	for (ULONG iEntry=0; iEntry < lpRecipients->cRows; iEntry++)
	{
		//we just skip empty recipient
		//according to the specification that is allowed
		if(0 == lpRecipients->aRow[iEntry].cValues)
		{
			continue;
		}

		// Size of spvProps - 1 for our PR_INSTANCE_KEY property
		if(lpRecipients->aRow[iEntry].cValues > 63)
		{
			hResult = E_OUTOFMEMORY;
			OTLKCON_DEBUGBREAK;
			break;
		}
		else
		{
			srRecipientRow.cValues = 0;
			ULONG cValues = lpRecipients->aRow[iEntry].cValues;
			std::set<ULONG> recipProps;
			LPSPropValue lpRecipProps = lpRecipients->aRow[iEntry].lpProps;
			LPSPropValue pKeyProp = NULL;
			LPSPropValue pInstanceProp = NULL;

			//That just to have every property only once
			for (ULONG iProp=0; iProp<cValues; iProp++)
			{
				if(recipProps.find(lpRecipProps[iProp].ulPropTag) == recipProps.end())
				{
					spvRecipientProps[srRecipientRow.cValues] = lpRecipProps[iProp];
					recipProps.insert(lpRecipProps[iProp].ulPropTag);

					if(PR_ROWID == lpRecipProps[iProp].ulPropTag)
					{
						pKeyProp = &spvRecipientProps[srRecipientRow.cValues];
					} // if
					if(PR_INSTANCE_KEY == lpRecipProps[iProp].ulPropTag)
					{
						pInstanceProp = &spvRecipientProps[srRecipientRow.cValues];
					} // if
					srRecipientRow.cValues++;
				} // if
			}

			//Check if property is messing
			if(NULL == pKeyProp)
			{
				hResult = E_INVALIDARG;
				break;
			}	

			// In case the PR_INSTANCE_KEY property is not found for this recipient, add it.
			if(NULL == pInstanceProp) 
			{
				// In the table of recipients, PR_INSTANCE_KEY and PR_ROWID have the same value
				spvRecipientProps[srRecipientRow.cValues].ulPropTag = PR_INSTANCE_KEY;
				spvRecipientProps[srRecipientRow.cValues].Value.bin.cb = sizeof(DWORD);
				spvRecipientProps[srRecipientRow.cValues].Value.bin.lpb = (LPBYTE)&pKeyProp->Value.l;
				srRecipientRow.cValues++;
			} // if
			else
			{
				pInstanceProp->Value.bin.cb = sizeof(DWORD);
				pInstanceProp->Value.bin.lpb = (LPBYTE)&pKeyProp->Value.l;
			}

			if(ulFlags & MODRECIP_MODIFY) 
			{
				TRACE_ROW(OTLKCON_LOG_TARGET_ALL, &srRecipientRow, L"O_IMessage::ModifyOrDeleteRecipients modify");
				hResult = m_lpRecipientsSnapShot->HrModifyRow(&srRecipientRow);
			} // if
			else if(ulFlags & MODRECIP_REMOVE)
			{
				TRACE_ROW(OTLKCON_LOG_TARGET_ALL, &srRecipientRow, L"O_IMessage::ModifyOrDeleteRecipients delete");
				hResult = m_lpRecipientsSnapShot->HrDeleteRow(pInstanceProp);
			} // else if
			
			if(FAILED(hResult))
			{
				break;
			} // if
			else
			{
				m_bRecipientsModified = TRUE;
			} // else
		}
	}

	return hResult;
}

STDMETHODIMP O_IMessage::CalculateDisplayProperties()
{
	std::string szToNames = "", szCcNames = "", szBccNames = "";
	LPSRowSet pRows = NULL;
	
	static SizedSPropTagArray(2, sptProps) =
	{
		2,
		{
			PR_RECIPIENT_TYPE,
			PR_DISPLAY_NAME
		}
	};
	
	LPMAPITABLE pTable = NULL;
	HRESULT hResult = m_lpRecipientsSnapShot->HrGetView(NULL, NULL, 0, &pTable);
	if(SUCCEEDED(hResult))
	{
		// Get All the recipient from the recipient table
		hResult = HrQueryAllRows (pTable,
			(LPSPropTagArray)&sptProps,
			NULL,
			NULL,
			0,
			&pRows);
		if(SUCCEEDED(hResult))
		{
			LPSPropValue pProps = NULL;
			std::string *lpTargetProp = NULL;
			
			for (ULONG i=0; i<pRows->cRows; i++)
			{
				lpTargetProp = NULL;
				pProps = pRows->aRow[i].lpProps;
				switch (pProps[0].Value.l)
				{
				case MAPI_TO:
					lpTargetProp = &szToNames;
					break;
				case MAPI_CC:
					lpTargetProp = &szCcNames;
					break;
				case MAPI_BCC:
					lpTargetProp = &szBccNames;
					break;
				}
				if(NULL != lpTargetProp)
				{
					if(!(*lpTargetProp).empty())
					{
						szToNames += "; ";
					} // if
					
					*lpTargetProp += pProps[1].Value.LPSZ;
				} // if
			}
			FreeProws(pRows);
		} // if
		pTable->Release();
	} // if
	
	// Set the new properties
	SPropValue spvProps[3] = {0};
	spvProps[0].ulPropTag = PR_DISPLAY_TO;
	spvProps[0].Value.LPSZ = (LPTSTR)szToNames.c_str();

	spvProps[1].ulPropTag = PR_DISPLAY_CC;
	spvProps[1].Value.LPSZ = (LPTSTR)szCcNames.c_str();

	spvProps[2].ulPropTag = PR_DISPLAY_BCC;
	spvProps[2].Value.LPSZ = (LPTSTR)szBccNames.c_str();

	TRACE_PROPVALUES(OTLKCON_LOG_TARGET_ALL, spvProps, 3, L"O_IMessage::CalculateDisplayProperties()");
	
	hResult = SetProps(3, spvProps, NULL);
	return hResult;
}

STDMETHODIMP O_IMessage::SaveAttachments()
{
    HRESULT hResult = S_OK;

    OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> O_IMessage::SaveAttachments");
    OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> O_IMessage::SaveAttachments");
 
	otlkcon_sqlite_helper * sqlObj = GetSqlObj();
	if(NULL == sqlObj)
	{
		hResult = E_FAIL;	
        goto saveattachments_end;
	} // if
	

	if(NULL != m_lpAttachmentsSnapShot)
	{
		//we need to delete all rows and after that 
		//insert a new set
		//that is very easy but may be we can optimize that in the future

		//We need to retrieve all data before deleting from permanent table
		LPMAPITABLE lpAttachments = NULL;
		hResult = m_lpAttachmentsSnapShot->HrGetView(NULL, NULL, 0, &lpAttachments);
		if(FAILED(hResult))
		{
			goto saveattachments_end;
		} // if
		
		LPSPropTagArray lpAllColumns = NULL;
		hResult = lpAttachments->QueryColumns(TBL_ALL_COLUMNS, &lpAllColumns);
		lpAttachments->SetColumns(lpAllColumns, 0);

		if(SUCCEEDED(hResult))
		{
			LPSRowSet rows = NULL;
			hResult = HrQueryAllRows(lpAttachments, NULL, NULL, NULL, 65535, &rows);
			if(SUCCEEDED(hResult))
			{
				sqlObj->sqlCmdBegin();
				
				hResult = attachmentTable->SetColumns(lpAllColumns, 0);
				if(SUCCEEDED(hResult))
				{
					ULONG cDeletedRows = 0;
					hResult = attachmentTable->HrDeleteRows(TAD_ALL_ROWS, NULL, &cDeletedRows);
					if(SUCCEEDED(hResult))
					{
						for(ULONG i = 0; i < rows->cRows; i++)
						{
							hResult = attachmentTable->HrModifyRow(&rows->aRow[i]);
							if(FAILED(hResult))
							{
								break;
							} // if
						} // for
					} // if
				} // if attachmentTable->SetColumns
				if(SUCCEEDED(hResult))
				{
					sqlObj->sqlCmdCommit();
				} // if
				else
				{
					sqlObj->sqlCmdRollback();
				} // else
				FreeProws(rows);
			} // if
			memObj->lpFreeBuffer(lpAllColumns);
			lpAttachments->Release();
		} // if
	} // if
	if(SUCCEEDED(hResult))
	{
		m_bAttachmentsModified = FALSE;
	} // if

saveattachments_end:
    OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> O_IMessage::SaveAttachments");
    OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> O_IMessage::SaveAttachments");
	return hResult;
}

STDMETHODIMP O_IMessage::SaveRecipients()
{
    HRESULT hResult = S_OK;

    OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> O_IMessage::SaveRecipients");
    OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> O_IMessage::SaveRecipients");

	otlkcon_sqlite_helper * sqlObj = GetSqlObj();
	if(NULL == sqlObj)
	{
		hResult = E_FAIL;
        goto saverecipients_end;
	} // if
	
	if(NULL != m_lpRecipientsSnapShot)
	{
		//we need to delete all rows and after that 
		//insert a new set
		//that is very easy but may be we can optimize that in the future

		//We need to retrieve all data before deleting from permanent table
		LPMAPITABLE lpRecipients = NULL;
		hResult = m_lpRecipientsSnapShot->HrGetView(NULL, NULL, 0, &lpRecipients);
		if(FAILED(hResult))
		{
			 goto saverecipients_end;
		} // if
		
		LPSPropTagArray lpAllColumns = NULL;
		hResult = lpRecipients->QueryColumns(TBL_ALL_COLUMNS, &lpAllColumns);
		lpRecipients->SetColumns(lpAllColumns, 0);

		if(SUCCEEDED(hResult))
		{
			LPSRowSet lpRecips = NULL;
			hResult = HrQueryAllRows(lpRecipients, NULL, NULL, NULL, 65535, &lpRecips);
			if(SUCCEEDED(hResult))
			{
				sqlObj->sqlCmdBegin();
				
				hResult = recipientTable->SetColumns(lpAllColumns, 0);
				if(SUCCEEDED(hResult))
				{
					ULONG cDeletedRows = 0;
					hResult = recipientTable->HrDeleteRows(TAD_ALL_ROWS, NULL, &cDeletedRows);
					if(SUCCEEDED(hResult))
					{
						for(ULONG iRecip = 0; iRecip < lpRecips->cRows; iRecip++)
						{
							hResult = recipientTable->HrModifyRow(&lpRecips->aRow[iRecip]);
							if(FAILED(hResult))
							{
								break;
							} // if
						} // for
					} // if
				} // if recipientTable->SetColumns
				if(SUCCEEDED(hResult))
				{
					sqlObj->sqlCmdCommit();
				} // if
				else
				{
					sqlObj->sqlCmdRollback();
				} // else
				FreeProws(lpRecips);
			} // if
			memObj->lpFreeBuffer(lpAllColumns);
			lpRecipients->Release();
		} // if
	} // if
	if(SUCCEEDED(hResult))
	{
		m_bRecipientsModified = FALSE;
	} // if

saverecipients_end:
    OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> O_IMessage::SaveRecipients");
    OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> O_IMessage::SaveRecipients");
	
	return hResult;
}

STDMETHODIMP O_IMessage::GetColumns(LPSRow lpRow, LPSPropTagArray *lppColumns)
{
	HRESULT hResult = E_INVALIDARG;
	if(NULL != lpRow && NULL != lppColumns)
	{
		*lppColumns = NULL;
		
		std::set<ULONG> uniqueTags;
		
		for(ULONG iProp = 0; iProp < lpRow->cValues; iProp++)
		{
			if(uniqueTags.find(lpRow->lpProps[iProp].ulPropTag) == uniqueTags.end())
			{
				uniqueTags.insert(lpRow->lpProps[iProp].ulPropTag);
			} // if	
		} // for

		hResult = memObj->lpAllocateBuffer(CbNewSPropTagArray((ULONG)uniqueTags.size()),
			(LPVOID*)lppColumns);
		if(SUCCEEDED(hResult))
		{
			(*lppColumns)->cValues = (ULONG)uniqueTags.size();
			std::set<ULONG>::iterator iTag = uniqueTags.begin();
			for(ULONG iCount = 0; iTag != uniqueTags.end(); iCount++, ++iTag)
			{
				(*lppColumns)->aulPropTag[iCount] = *iTag;
			} // for
		} // if
	} // if
	return hResult;
}

STDMETHODIMP O_IMessage::ICalExport( wchar_t **iCal )
{
    HRESULT result = S_OK;

    return result;
}

STDMETHODIMP O_IMessage::ICalImport( wchar_t *iCal )
{
    HRESULT result = S_OK;
    icalcomponent *comp = NULL;
    char *iCalStr;

    iCalStr = (char *)memObj->pMalloc( (ULONG)wcslen(iCal)+1 );
    _snprintf(iCalStr, wcslen(iCal), "%S", iCal);

    comp = icalparser_parse_string( iCalStr );
    result = ICalImport( comp );

    return result;
}

STDMETHODIMP O_IMessage::ICalExport( icalcomponent **iCal )
{
    HRESULT result = S_OK;

    return result;
}

STDMETHODIMP O_IMessage::ICalImport( icalcomponent *iCal )
{
    HRESULT result = S_OK;
    SPropValue icalProps[30] = {0};
    ULONG propCount = 0;
    const char *tmpStr;
    icalcomponent *event;
    struct icaltimetype tmpTime = {0};
    FILETIME ft = {0};

    event = icalcomponent_get_first_component( iCal, ICAL_VEVENT_COMPONENT );
    tmpStr = icalcomponent_get_description( event );

    if( tmpStr )
    {
        icalProps[propCount].ulPropTag      = PR_DISPLAY_NAME;
        icalProps[propCount++].Value.lpszA  = memObj->_strdup( tmpStr );
    }

    tmpTime = icalcomponent_get_dtstart( event );
    result = icaltime_as_filetime( tmpTime, &ft );

    icalProps[propCount].ulPropTag      = 0x820D0040;
    icalProps[propCount++].Value.ft     = ft;

    tmpTime = icalcomponent_get_dtend( event );
    result = icaltime_as_filetime( tmpTime, &ft );

    icalProps[propCount].ulPropTag      = 0x820E0040;
    icalProps[propCount++].Value.ft     = ft;

    result = _IntSetProps(propCount,(LPSPropValue)&icalProps,NULL);

    result = SaveChanges(NULL);

    return result;
}

#include "mimedir_tlb.h"

STDMETHODIMP O_IMessage::PreICalImportMIMEDIR( string *iCal )
{
	HRESULT hResult = E_FAIL;
	//there we need to check some elements required by Outlook convertor
	icalcomponent *comp = NULL;

	comp = icalparser_parse_string( iCal->c_str() );
	if(NULL != comp)
	{
		BOOL bModified = FALSE;
		
		if(ICAL_METHOD_NONE == icalcomponent_get_method(comp)) 
		{
			//So far we use PUBLISH method
			icalcomponent_set_method(comp, ICAL_METHOD_PUBLISH);
			bModified = TRUE;
		} // if
		
		
		icalcomponent *inner = icalcomponent_get_inner(comp);
		icalproperty *prop 
			= icalcomponent_get_first_property(inner,ICAL_DTSTAMP_PROPERTY);

		if (NULL == prop)
		{
			icalcomponent_set_dtstamp(comp, icaltime_current_time_with_zone(NULL));
			bModified = TRUE;
		}
		hResult = S_OK;
		if(bModified) 
		{
			char* lpString = icalcomponent_as_ical_string(comp);
			*iCal = lpString;
			//See comments in PostICalExportMIMEDIR
			//icalmemory_free_buffer(lpString);
			hResult = S_FALSE;
		} // if
		icalcomponent_free(comp);
	} // if

	return hResult;
}

STDMETHODIMP O_IMessage::ICalImportMIMEDIR( wchar_t *iCal , O_IFolder* lpParentFolder)
{
	HRESULT hResult = S_OK;
	MIMEDIR::IMDCvt_iCal* lpConvertor = NULL;
	BOOL bConverted = FALSE;
	bstr_t bstrICAL = iCal;
	
	string strICal = (LPCSTR)bstrICAL;
	hResult = PreICalImportMIMEDIR(&strICal);
	if(FAILED(hResult))
	{
		return E_FAIL;
	} // if
	
	hResult = CoCreateInstance(
		MIMEDIR::CLSID_MapiCvt,
		NULL,
		CLSCTX_INPROC_SERVER,
		MIMEDIR::IID_IMDCvt_iCal,
		(LPVOID*)&lpConvertor);
	if(SUCCEEDED(hResult))
	{
		
		LPSTREAM lpStream = NULL;
		hResult = CreateStreamOnHGlobal(NULL, TRUE, &lpStream);
//		hResult = OpenStreamOnFile(memObj->lpAllocateBuffer, memObj->lpFreeBuffer, STGM_READ, "C:\\Documents and Settings\\henry\\My Documents\\test4.ics", NULL, &lpStream);
		if(SUCCEEDED(hResult))
		{
			ULONG ulActual = 0;
			hResult = lpStream->Write(strICal.c_str(), strICal.length(), &ulActual);
			
			if(SUCCEEDED(hResult))
			{
				LARGE_INTEGER liBegin = {0};
				hResult = lpStream->Seek(liBegin, STREAM_SEEK_SET, NULL);
				if(SUCCEEDED(hResult))
				{
					LPADRBOOK lpAddressBook = NULL;
					hResult = msgstore->lpMAPISup->OpenAddressBook(NULL, 0, &lpAddressBook);
					if(SUCCEEDED(hResult))
					{
						hResult = lpConvertor->IcalStreamToMessage(
							lpStream, 
							(MIMEDIR::IMAPIFolder*)lpParentFolder,
							(MIMEDIR::IMessage*)this,
							(MIMEDIR::IAddrBook*)lpAddressBook, 0);
						if(SUCCEEDED(hResult))
						{
							bConverted = TRUE;
							PostICalImportMIMEDIR(iCal);
							SaveChanges(0);
						} // if
						else
						{
							ULARGE_INTEGER uliCurrent = {0};
							lpStream->Seek(liBegin, STREAM_SEEK_CUR, &uliCurrent);
							uliCurrent = uliCurrent;
						} // else
						lpAddressBook->Release();
					} // if
				} // if
			} // if
			lpStream->Release();
		} // if

		lpConvertor->Release();
	} // if

	if(!bConverted) 
	{
		hResult = ICalImport(iCal);
	} // if

	return hResult;
}

const static MAPIUID PropSetID1_UID = {0x02, 0x20, 0x06, 0x00, \
	0x00, 0x00, 0x00, 0x00, \
	0xC0, 0x00, 0x00, 0x00, \
	0x00, 0x00, 0x00, 0x46 }; // Used in appointment item properties. Message class:IPM.Appointment 
#ifndef APPOINTMENT_PROPS_GUID
#define APPOINTMENT_PROPS_GUID PropSetID1_UID
#endif

#ifndef PR_Appt_Location
#define PR_Appt_Location 0x8208 // Item.Location 
#define PR_Appt_Location_GUID APPOINTMENT_PROPS_GUID
#define PR_Appt_Location_DEF {(LPGUID)&PR_Appt_Location_GUID, MNID_ID, PR_Appt_Location}
#endif

STDMETHODIMP O_IMessage::PostICalImportMIMEDIR( wchar_t *iCal )
{
	HRESULT result = S_OK;
	SPropValue icalProps[30] = {0};
	ULONG propCount = 0;
	const char *lpszDescription = NULL;
	const char *lpszSummary = NULL;
	const char *lpszLocation = NULL;
	icalcomponent *event;
	struct icaltimetype tmpTime = {0};
	FILETIME ft = {0};
	icalcomponent *comp = NULL;
	char *iCalStr = NULL;

	iCalStr = (char *)memObj->pMalloc( (ULONG)wcslen(iCal)+1 );
	_snprintf(iCalStr, wcslen(iCal), "%S", iCal);

	comp = icalparser_parse_string( iCalStr );

	if(NULL != comp)
	{
		event = icalcomponent_get_first_component( comp, ICAL_VEVENT_COMPONENT );
		if(NULL != event)
		{
			lpszSummary = icalcomponent_get_summary( event );
			lpszLocation = icalcomponent_get_location( event );
			lpszDescription = icalcomponent_get_description( event );

			BOOL bBodyChanged = (NULL != lpszDescription);

			if( lpszSummary )
			{
				icalProps[propCount].ulPropTag      = PR_SUBJECT;
				icalProps[propCount++].Value.lpszA  = (LPSTR)lpszSummary;

				icalProps[propCount].ulPropTag      = PR_CONVERSATION_TOPIC;
				icalProps[propCount++].Value.lpszA  = (LPSTR)lpszSummary;
			}

			if( lpszDescription )
			{
				icalProps[propCount].ulPropTag      = PR_BODY;
				icalProps[propCount++].Value.lpszA  = (LPSTR)lpszDescription;
			}

			if( lpszLocation )
			{
				//Obtain named tag for Location property
				static const MAPINAMEID LocationName = PR_Appt_Location_DEF;
				static const LPMAPINAMEID LocationNameArray[1] = {
					(LPMAPINAMEID)&LocationName
				};
				LPSPropTagArray lpLocationPropArray = NULL;
				HRESULT hResult = GetIDsFromNames(
					1,
					(LPMAPINAMEID*)LocationNameArray,
					MAPI_CREATE,
					&lpLocationPropArray);
				if(S_OK == hResult) 
				{
					icalProps[propCount].ulPropTag      = lpLocationPropArray->aulPropTag[0] | PT_STRING8;
					icalProps[propCount++].Value.lpszA  = (LPSTR)lpszLocation;

					memObj->free(lpLocationPropArray);
				}
			}

			if(0 < propCount) 
			{
				result = _IntSetProps(propCount,(LPSPropValue)&icalProps, NULL);
			} // if

			if(bBodyChanged) 
			{
				BOOL bChanged = FALSE;
				result = RTFSync((LPMESSAGE)this , RTF_SYNC_BODY_CHANGED, &bChanged);
			} // if
		} // if NULL != event
		
		icalcomponent_free(comp);
	} // if NULL != comp
	
	memObj->free(iCalStr);
	return result;
}

STDMETHODIMP O_IMessage::ICalExportMIMEDIR( wchar_t **iCal )
{
	HRESULT hResult = S_OK;
	//First we need to prepare arguments for MIMEDIR converter
	LPMALLOC lpMalloc = MAPIGetDefaultMalloc();
	LPSTREAM lpStream = NULL;
    char *iCalA = NULL;

    *iCal = NULL;

	hResult = CreateStreamOnHGlobal(NULL, TRUE, &lpStream);
	if(SUCCEEDED(hResult))
	{
		LPSTORAGE lpStorage = NULL;
		hResult = HrIStorageFromStream(lpStream, NULL, STGSTRM_CREATE|STGSTRM_MODIFY, &lpStorage);
		if(SUCCEEDED(hResult))
		{
			LPMESSAGE lpStgMessage = NULL;
			hResult = OpenIMsgOnIStg(
				NULL,
				memObj->lpAllocateBuffer, memObj->lpAllocateMore, memObj->lpFreeBuffer,
				lpMalloc, 
				msgstore->lpMAPISup,
				lpStorage,
				NULL, 0,
				0,
				&lpStgMessage);
			if(SUCCEEDED(hResult))
			{
				hResult = CopyTo(0, NULL, NULL, 0, NULL, &IID_IMessage, lpStgMessage, 0, NULL);
				if(SUCCEEDED(hResult))
				{
					MIMEDIR::IMDCvt_iCal* lpConvertor = NULL;
					
					hResult = CoCreateInstance(
						MIMEDIR::CLSID_MapiCvt,
						NULL,
						CLSCTX_INPROC_SERVER,
						MIMEDIR::IID_IMDCvt_iCal,
						(LPVOID*)&lpConvertor);
					if(SUCCEEDED(hResult))
					{
						LPADRBOOK lpAddressBook = NULL;
						hResult = msgstore->lpMAPISup->OpenAddressBook(NULL, 0, &lpAddressBook);
						if(SUCCEEDED(hResult))
						{
							LPSTREAM lpResultStream = NULL;
							hResult = CreateStreamOnHGlobal(NULL, TRUE, &lpResultStream);
							if(SUCCEEDED(hResult))
							{
								hResult = lpConvertor->MessageToIcalStream(
									(MIMEDIR::IMessage*)lpStgMessage,
									lpResultStream,
									(MIMEDIR::IAddrBook*)lpAddressBook, 
									NULL);
								if(SUCCEEDED(hResult))
								{
									LARGE_INTEGER liBegin = {0};
									lpResultStream->Seek(liBegin, STREAM_SEEK_SET, NULL);
									
									STATSTG st = {0};
									hResult = lpResultStream->Stat(&st, STATFLAG_NONAME);
									if(SUCCEEDED(hResult))
									{
										//We are taking only low part of stream's size
										ULONG cbSize = (ULONG)st.cbSize.QuadPart;
										iCalA = (LPSTR)memObj->malloc(cbSize+1);
										if(NULL != iCalA)
										{
											ULONG ulActual = 0;
											hResult = lpResultStream->Read(iCalA, cbSize, &ulActual);
											if(SUCCEEDED(hResult))
											{
                                                *iCal = (wchar_t *)memObj->malloc( (cbSize+1)*sizeof(wchar_t) );
                                                swprintf(*iCal, L"%S", iCalA );
											} // if
											memObj->free(iCalA);
										} // if
									} // if
								} // if
								lpResultStream->Release();
							} // if
							lpAddressBook->Release();
						} // if	
						lpConvertor->Release();
					} // if
				} // if
				lpStgMessage->Release();
			} // if
			lpStorage->Release();
		} // if
		lpStream->Release();
	} // if

	if(NULL != *iCal)
	{
		hResult = PostICalExportMIMEDIR(iCal);
	} // if

	return hResult;
}

STDMETHODIMP O_IMessage::PostICalExportMIMEDIR( wchar_t **iCal )
{
	HRESULT hResult = S_OK;
	
	icalcomponent *comp = NULL;
	char *iCalStr = NULL;

	iCalStr = (char *)memObj->pMalloc( (ULONG)wcslen(*iCal)+1 );
	_snprintf(iCalStr, wcslen(*iCal), "%S", *iCal);

	comp = icalparser_parse_string( iCalStr );
	if(NULL != comp)
	{
		//Outlook XP does not create UID for appointment 
		//if that appointment is in not default calendar folder
		//if UID is missing Oracle's caldav server fails to create new appointment.
		if(NULL == icalcomponent_get_uid(comp))
		{
			GUID g;
			CoCreateGuid(&g);
			wchar_t szUID[MAX_PATH+1] = {0};
			StringFromGUID2(g, szUID, sizeof(szUID)/sizeof(szUID[0]));
			char szUIDA[MAX_PATH+1] = {0};
			_snprintf(szUIDA, sizeof(szUIDA), "%S", szUID);
			icalcomponent_set_uid(comp,szUIDA);
			
			char* iCalA = icalcomponent_as_ical_string(comp);
			memObj->free(*iCal);
			*iCal = (wchar_t *)memObj->malloc( (ULONG)(strlen(iCalA)+1)*sizeof(wchar_t));
			_snwprintf(*iCal, (strlen(iCalA)+1)*sizeof(wchar_t), L"%S", iCalA);
			
			//this buffer is in temp ring so we cannot free it in such way
			//icalmemory_free_buffer(iCalA);
		} // if
		icalcomponent_free(comp);
	} // if
	if(NULL != iCalStr)
	{
		memObj->pFree(iCalStr);
	} // if
	return hResult;
}

HRESULT O_IMessage::BeginUploading()
{
	OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> O_IMessage::BeginUploading()");
	OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> O_IMessage::BeginUploading()");
	HRESULT hResult = S_OK;

	SPropValue spvUploading = {0};
	spvUploading.ulPropTag = PR_OTLKCON_UPLOAD;
	spvUploading.Value.b = TRUE;

	hResult = _StoreOneProp(&spvUploading);
	OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> O_IMessage::BeginUploading()");
	OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> O_IMessage::BeginUploading()");
	return hResult;
}

HRESULT O_IMessage::CompleteUploading()
{
	OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> O_IMessage::CompleteUploading()");
	OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> O_IMessage::CompleteUploading()");
	HRESULT hResult = S_OK;

	SPropValue spvUploading = {0};
	spvUploading.ulPropTag = PR_OTLKCON_UPLOAD;
	spvUploading.Value.b = FALSE;

	hResult = _StoreOneProp(&spvUploading);
	OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> O_IMessage::CompleteUploading()");
	OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> O_IMessage::CompleteUploading()");
	return hResult;
}

BOOL O_IMessage::IsUploading()
{
	OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> O_IMessage::IsUploading()");
	OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> O_IMessage::IsUploading()");
	
	BOOL bResult = FALSE;
	LPSPropValue lpUpload = NULL;
	
	HRESULT hResult = O_IProp::_QueryOneProp(PR_OTLKCON_UPLOAD, &lpUpload);
	if(SUCCEEDED(hResult) && NULL != lpUpload && 
		PT_ERROR != PROP_TYPE(lpUpload->ulPropTag) &&
		lpUpload->Value.b)
	{
		bResult = TRUE;
	} // if
	if(NULL != lpUpload)
	{
		memObj->free(lpUpload);
	} // if
	
	OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> O_IMessage::IsUploading()");
	OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> O_IMessage::IsUploading()");
	return bResult;
}

BOOL O_IMessage::IsDownloading()
{
	OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> O_IMessage::IsDownloading()");
	OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> O_IMessage::IsDownloading()");
	
	BOOL bResult = FALSE;
	LPSPropValue lpDownload = NULL;
	HRESULT hResult = O_IProp::_QueryOneProp(PR_OTLKCON_DOWNLOAD, &lpDownload);
	if(SUCCEEDED(hResult) && NULL != lpDownload && 
		PT_ERROR != PROP_TYPE(lpDownload->ulPropTag) &&
		lpDownload->Value.b)
	{
		bResult = TRUE;
	} // if
	if(NULL != lpDownload)
	{
		memObj->free(lpDownload);
	} // if

	OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> O_IMessage::IsDownloading()");
	OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> O_IMessage::IsDownloading()");
	return bResult;
}

HRESULT O_IMessage::BeginDownloading()
{
	OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> O_IMessage::BeginDownloading()");
	OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> O_IMessage::BeginDownloading()");
	HRESULT hResult = S_OK;

	SPropValue spvDownloading = {0};
	spvDownloading.ulPropTag = PR_OTLKCON_DOWNLOAD;
	spvDownloading.Value.b = TRUE;

	hResult = _StoreOneProp(&spvDownloading);

	OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> O_IMessage::BeginDownloading()");
	OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> O_IMessage::BeginDownloading()");
	return hResult;
}

HRESULT O_IMessage::CompleteDownloading()
{
	OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> O_IMessage::CompleteDownloading()");
	OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> O_IMessage::CompleteDownloading()");
	HRESULT hResult = S_OK;

	SPropValue spvDownloading = {0};
	spvDownloading.ulPropTag = PR_OTLKCON_DOWNLOAD;
	spvDownloading.Value.b = FALSE;

	hResult = _StoreOneProp(&spvDownloading);

	OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> O_IMessage::CompleteDownloading()");
	OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> O_IMessage::CompleteDownloading()");
	return hResult;
}

BOOL O_IMessage::HasForeignID()
{
	OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> O_IMessage::HasForeignID()");
	OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> O_IMessage::HasForeignID()");

	BOOL bResult = FALSE;
	LPSPropValue lpForeignID = NULL;
	HRESULT hResult = O_IProp::_QueryOneProp(PR_OTLKCON_FOREIGN_ID, &lpForeignID);
	if(SUCCEEDED(hResult) && NULL != lpForeignID && 
		PT_ERROR != PROP_TYPE(lpForeignID->ulPropTag))
	{
		bResult = TRUE;
	} // if
	if(NULL != lpForeignID)
	{
		memObj->free(lpForeignID);
	} // if

	OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> O_IMessage::HasForeignID()");
	OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> O_IMessage::HasForeignID()");
	return bResult;
}

HRESULT O_IMessage::CreateNewForeignID()
{
	OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> O_IMessage::CompleteDownloading()");
	OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> O_IMessage::CompleteDownloading()");
	HRESULT hResult = S_OK;
	char str[MAX_PATH+1] = {0};
	MAPIUID newID = {0};
	NewMAPIUID(&newID);
	_snprintf(str, MAX_PATH,
		"%0.2X%0.2X%0.2X%0.2X-%0.2X%0.2X-%0.2X%0.2X-%0.2X%0.2X-%0.2X%0.2X%0.2X%0.2X%0.2X%0.2X.ics",
		newID.ab[0], newID.ab[1], newID.ab[2], newID.ab[3], newID.ab[4],
		newID.ab[5], newID.ab[6], newID.ab[7], newID.ab[8], newID.ab[9],
		newID.ab[10], newID.ab[11], newID.ab[12], newID.ab[13], newID.ab[14],
		newID.ab[15]);
	
	SPropValue spvForeignID = {0};
	spvForeignID.ulPropTag = PR_OTLKCON_FOREIGN_ID;
	spvForeignID.Value.lpszA = str;

	hResult = _StoreOneProp(&spvForeignID);

	OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> O_IMessage::CompleteDownloading()");
	OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> O_IMessage::CompleteDownloading()");
	return hResult;
}

HRESULT O_IMessage::IsPropertyChanged(ULONG ulPropTag, LPBOOL lpbChanged)
{
	HRESULT hResult = E_INVALIDARG;
	if(NULL != lpbChanged) 
	{
		hResult = S_OK;

		*lpbChanged = FALSE;
		otlkcon_sqlite_helper * sqlObj = GetSqlObj();
		if(NULL != sqlObj)
		{
			int iRows = 0;
			HRESULT sqlRes = sqlObj->sqlCmdSingleInt(
				&iRows, 
				0,
				"SELECT count(*) FROM %s WHERE propTag = %d \
				AND TRANSACT_ID = %d ", tableName, ulPropTag, objNum);
			if(SUCCEEDED(sqlRes) && 0 < iRows)
			{
				*lpbChanged = TRUE;
			} // if
		} // if database valid
		else
		{
			hResult = E_FAIL;
		} // else
	} // if

	return hResult;
}

HRESULT O_IMessage::SynchronizeBody()
{
	//we need to synchronize RTF and plain text body
	//we need to determine what body is changed
	//and call RTFSync()
	ULONG ulFlags = 0;
	BOOL bChanged = FALSE;
	HRESULT hResult = IsPropertyChanged(PR_BODY, &bChanged);
	if(SUCCEEDED(hResult))
	{
		if(bChanged) 
		{
			ulFlags |= RTF_SYNC_BODY_CHANGED;
		} // if
		hResult = IsPropertyChanged(PR_RTF_COMPRESSED, &bChanged);
		if(SUCCEEDED(hResult))
		{
			if(bChanged) 
			{
				ulFlags |= RTF_SYNC_RTF_CHANGED;
			} // if
		} // if
	} // if
	
	if(SUCCEEDED(hResult))
	{
		BOOL bMessageUpdated = FALSE;
		hResult = RTFSync((LPMESSAGE)this, ulFlags, &bMessageUpdated);
	} // if
	if(SUCCEEDED(hResult))
	{
		BOOL bNeedSync = FALSE;
		BOOL bFromRtf = FALSE;
		LPSPropValue lpInSync = NULL;
		if(SUCCEEDED(HrGetOneProp(this, PR_RTF_IN_SYNC, &lpInSync)))
		{
			bNeedSync = !lpInSync->Value.b;
			if(!bNeedSync) 
			{
				bFromRtf = TRUE;
			} // if
			memObj->free(lpInSync);
		} // if
		else
		{
			bNeedSync = TRUE;
		} // else
		
		if(bNeedSync) 
		{
			if(bFromRtf) 
			{
				//create plain body from Rtf
			} // if
			else
			{
				//create rtf body from plain
			} // else
		} // if
	} // if
	return hResult;
}

